﻿using System;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Drawing;
using Microsoft.Win32;
using System.Drawing.Imaging;
using System.IO;
using System.Collections.Generic;

namespace RuliChainCodeImageGenerator
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        private string m_RuliChainCode;
        private char[] m_AllowedChars = {'L', 'R', 'U', 'I'};
        private Bitmap m_Result;
        private int m_MaxWidth;
        private int m_MaxHeight;
        private Bitmap m_Img;
        private System.Drawing.Color m_Col;

        private List<BitMask> m_ListBitMasks;
        private BitMask m_SelectedBitMask;

        public MainWindow()
        {
            InitializeComponent();

            m_ListBitMasks = new List<BitMask>();
            m_ListBitMasks.Add(new BitMask() { Title = "3x3", Offset = 2, Size = 3 });
            m_ListBitMasks.Add(new BitMask() { Title = "5x5", Offset = 3, Size = 5 });
            m_ListBitMasks.Add(new BitMask() { Title = "7x7", Offset = 4, Size = 7 });

            cbBitMask.ItemsSource = m_ListBitMasks;
            cbBitMask.SelectedIndex = 1;
        }

        private void drawLine(direction _dct, int _x, int _y)
        {
            switch (_dct)
            {
                case direction.LEFT:
                    {
                        for (int i = 0; i < m_SelectedBitMask.Size; i++)
                        {
                            m_Img.SetPixel(_x - i, _y, m_Col);
                        }
                            break;
                    }
                case direction.TOP:
                    {
                        for (int i = 0; i < m_SelectedBitMask.Size; i++)
                        {
                            m_Img.SetPixel(_x, _y - i, m_Col);
                        }
                        break;
                    }
                case direction.RIGHT:
                    {
                        for (int i = 0; i < m_SelectedBitMask.Size; i++)
                        {
                            m_Img.SetPixel(_x + i, _y, m_Col);
                        }
                        break;
                    }
                case direction.BOTTOM:
                    {
                        for (int i = 0; i < m_SelectedBitMask.Size; i++)
                        {
                            m_Img.SetPixel(_x, _y + i, m_Col);
                        }

                        break;
                    }
            }
        }

        private void drawCurve(direction _dct, char _newDct, int _x, int _y)
        {
            direction newDct = direction.TOP;

            #region set new direction enum
            switch (_newDct)
            {
                case 'L':
                    {
                        newDct = direction.LEFT;
                        break;
                    }
                case 'T':
                    {
                        newDct = direction.TOP;
                        break;
                    }
                case 'R':
                    {
                        newDct = direction.RIGHT;
                        break;
                    }
                case 'B':
                    {
                        newDct = direction.BOTTOM;
                        break;
                    }
            } 
            #endregion

            if (_dct == direction.TOP)
            {
                if (newDct == direction.RIGHT)
                    drawActualCurve(Curve.LB, _x + (m_SelectedBitMask.Offset-1), _y - (m_SelectedBitMask.Offset-1), m_SelectedBitMask);
                else if (newDct == direction.LEFT)
                    drawActualCurve(Curve.RB, _x - (m_SelectedBitMask.Offset-1), _y - (m_SelectedBitMask.Offset-1), m_SelectedBitMask);
            }
            else if (_dct == direction.BOTTOM)
            {
                if (newDct == direction.RIGHT)
                    drawActualCurve(Curve.RT, _x - (m_SelectedBitMask.Offset-1), _y + (m_SelectedBitMask.Offset-1), m_SelectedBitMask);
                else if (newDct == direction.LEFT)
                    drawActualCurve(Curve.LT, _x + (m_SelectedBitMask.Offset-1), _y + (m_SelectedBitMask.Offset-1), m_SelectedBitMask);
            }
            else if (_dct == direction.LEFT)
            {
                if (newDct == direction.TOP || newDct == direction.RIGHT)
                    drawActualCurve(Curve.LT, _x, _y, m_SelectedBitMask);
                else if (newDct == direction.BOTTOM || newDct == direction.LEFT)
                    drawActualCurve(Curve.LB, _x, _y, m_SelectedBitMask);
            }
            else if (_dct == direction.RIGHT)
            {
                if (newDct == direction.TOP || newDct == direction.LEFT)
                    drawActualCurve(Curve.RT, _x, _y, m_SelectedBitMask);
                else if (newDct == direction.BOTTOM || newDct == direction.RIGHT)
                    drawActualCurve(Curve.RB, _x, _y, m_SelectedBitMask);
            }
        }

        private void drawActualCurve(Curve _curve, int _x, int _y, BitMask _bm)
        {
            switch (_curve)
            {
                case Curve.RB:
                    {
                        switch (_bm.Size)
                        {
                            case 3:
                                {
                                    m_Img.SetPixel(_x, _y, m_Col);
                                    m_Img.SetPixel(_x + 1, _y, m_Col);
                                    m_Img.SetPixel(_x + 1, _y + 1, m_Col);
                                    break;
                                }
                            case 5:
                                {
                                    m_Img.SetPixel(_x, _y, m_Col);
                                    m_Img.SetPixel(_x + 1, _y, m_Col);
                                    m_Img.SetPixel(_x + 2, _y + 1, m_Col);
                                    m_Img.SetPixel(_x + 2, _y + 2, m_Col);
                                    break;
                                }
                            case 7:
                                {
                                    m_Img.SetPixel(_x, _y, m_Col);
                                    m_Img.SetPixel(_x + 1, _y, m_Col);
                                    m_Img.SetPixel(_x + 2, _y + 1, m_Col);
                                    m_Img.SetPixel(_x + 3, _y + 2, m_Col);
                                    m_Img.SetPixel(_x + 3, _y + 3, m_Col);
                                    break;
                                }
                        }
                        break;
                    }
                case Curve.RT:
                    {
                        switch (_bm.Size)
                        {
                            case 3:
                                {
                                    m_Img.SetPixel(_x, _y, m_Col);
                                    m_Img.SetPixel(_x + 1, _y, m_Col);
                                    m_Img.SetPixel(_x + 1, _y - 1, m_Col);
                                    break;
                                }
                            case 5:
                                {
                                    m_Img.SetPixel(_x, _y, m_Col);
                                    m_Img.SetPixel(_x + 1, _y, m_Col);
                                    m_Img.SetPixel(_x + 2, _y - 1, m_Col);
                                    m_Img.SetPixel(_x + 2, _y - 2, m_Col);
                                    break;
                                }
                            case 7:
                                {
                                    m_Img.SetPixel(_x, _y, m_Col);
                                    m_Img.SetPixel(_x + 1, _y, m_Col);
                                    m_Img.SetPixel(_x + 2, _y - 1, m_Col);
                                    m_Img.SetPixel(_x + 3, _y - 2, m_Col);
                                    m_Img.SetPixel(_x + 3, _y - 3, m_Col);
                                    break;
                                }
                        }
                        break;
                    }
                case Curve.LB:
                    {
                        switch (_bm.Size)
                        {
                            case 3:
                                {
                                    m_Img.SetPixel(_x, _y, m_Col);
                                    m_Img.SetPixel(_x - 1, _y, m_Col);
                                    m_Img.SetPixel(_x - 1, _y + 1, m_Col);
                                    break;
                                }
                            case 5:
                                {
                                    m_Img.SetPixel(_x, _y, m_Col);
                                    m_Img.SetPixel(_x - 1, _y, m_Col);
                                    m_Img.SetPixel(_x - 2, _y + 1, m_Col);
                                    m_Img.SetPixel(_x - 2, _y + 2, m_Col);
                                    break;
                                }
                            case 7:
                                {
                                    m_Img.SetPixel(_x, _y, m_Col);
                                    m_Img.SetPixel(_x - 1, _y, m_Col);
                                    m_Img.SetPixel(_x - 2, _y + 1, m_Col);
                                    m_Img.SetPixel(_x - 3, _y + 2, m_Col);
                                    m_Img.SetPixel(_x - 3, _y + 3, m_Col);
                                    break;
                                }
                        }
                        break;
                    }
                case Curve.LT:
                    {
                        switch (_bm.Size)
                        {
                            case 3:
                                {
                                    m_Img.SetPixel(_x, _y, m_Col);
                                    m_Img.SetPixel(_x - 1, _y, m_Col);
                                    m_Img.SetPixel(_x - 1, _y - 1, m_Col);
                                    break;
                                }
                            case 5:
                                {
                                    m_Img.SetPixel(_x, _y, m_Col);
                                    m_Img.SetPixel(_x - 1, _y, m_Col);
                                    m_Img.SetPixel(_x - 2, _y - 1, m_Col);
                                    m_Img.SetPixel(_x - 2, _y - 2, m_Col);
                                    break;
                                }
                            case 7:
                                {
                                    m_Img.SetPixel(_x, _y, m_Col);
                                    m_Img.SetPixel(_x - 1, _y, m_Col);
                                    m_Img.SetPixel(_x - 2, _y - 1, m_Col);
                                    m_Img.SetPixel(_x - 3, _y - 2, m_Col);
                                    m_Img.SetPixel(_x - 3, _y - 3, m_Col);
                                    break;
                                }
                        }
                        break;
                    }
            }
        }

        private void btCreateImage_Click(object sender, RoutedEventArgs e)
        {
            #region check input
            m_RuliChainCode = tbRuliInput.Text;
            m_RuliChainCode = m_RuliChainCode.Replace(" ", "");
            m_RuliChainCode = m_RuliChainCode.ToUpper();
            tbRuliInput.Text = m_RuliChainCode;

            if (string.IsNullOrEmpty(m_RuliChainCode.Trim()))
                return;

            foreach (var item in m_RuliChainCode.ToCharArray())
            {
                if (!m_AllowedChars.Contains(item))
                {
                    MessageBox.Show("No valid RULI chain code!");
                    return;
                }
            } 
            #endregion

            #region initialize
            m_MaxWidth = (m_RuliChainCode.Length + 2) * m_SelectedBitMask.Size + 10;
            m_MaxHeight = (m_RuliChainCode.Length + 2) * m_SelectedBitMask.Size + 10;

            m_Img = new Bitmap(m_MaxWidth, m_MaxHeight);

            int x, y;
            x = m_MaxWidth/2;
            y = m_MaxHeight / 2;
            direction dct = direction.TOP;
            m_Col = System.Drawing.Color.White;

            int startX = 0;
            int startY = 0;
            int endX = 0;
            int endY = 0;
            #endregion

            #region draw image
            foreach (var item in m_RuliChainCode.ToCharArray())
            {
                switch (dct)
                {
                    #region LEFT
                    case direction.LEFT:
                        {
                            switch (item)
                            {
                                case 'L':
                                    {
                                        dct = direction.BOTTOM;

                                        drawCurve(direction.LEFT, 'L', x, y);

                                        x -= m_SelectedBitMask.Offset-1;
                                        y += m_SelectedBitMask.Offset;

                                        break;
                                    }
                                case 'R':
                                    {
                                        dct = direction.TOP;

                                        drawCurve(direction.LEFT, 'R', x, y);

                                        x -= m_SelectedBitMask.Offset-1;
                                        y -= m_SelectedBitMask.Offset;

                                        break;
                                    }
                                case 'I':
                                    {
                                        dct = direction.LEFT;
                                        drawLine(direction.LEFT, x, y);

                                        x -= m_SelectedBitMask.Size;

                                        break;
                                    }
                            }
                            break;
                        }
                    #endregion

                    #region TOP
                    case direction.TOP:
                        {
                            switch (item)
                            {
                                case 'L':
                                    {
                                        dct = direction.LEFT;

                                        drawCurve(direction.TOP, 'L', x, y);

                                        x -= m_SelectedBitMask.Offset;
                                        y -= m_SelectedBitMask.Offset-1;

                                        break;
                                    }
                                case 'R':
                                    {
                                        dct = direction.RIGHT;

                                        drawCurve(direction.TOP, 'R', x, y);

                                        x += m_SelectedBitMask.Offset;
                                        y -= m_SelectedBitMask.Offset - 1;

                                        break;
                                    }
                                case 'I':
                                    {
                                        dct = direction.TOP;

                                        drawLine(direction.TOP, x, y);

                                        y -= m_SelectedBitMask.Size;

                                        break;
                                    }
                            }
                            break;
                        }
                    #endregion

                    #region RIGHT
                    case direction.RIGHT:
                        {
                                switch (item)
                                {
                                    case 'L':
                                        {
                                            dct = direction.TOP;

                                            drawCurve(direction.RIGHT, 'L', x, y);

                                            x += m_SelectedBitMask.Offset - 1;
                                            y -= m_SelectedBitMask.Offset;

                                            break;
                                        }
                                    case 'R':
                                        {
                                            dct = direction.BOTTOM;

                                            drawCurve(direction.RIGHT, 'R', x, y);

                                            x += m_SelectedBitMask.Offset - 1;
                                            y += m_SelectedBitMask.Offset;

                                            break;
                                        }
                                    case 'I':
                                        {
                                            dct = direction.RIGHT;

                                            drawLine(direction.RIGHT, x, y);

                                            x += m_SelectedBitMask.Size;

                                            break;
                                        }
                                }
                            break;
                        }
                    #endregion

                    #region BOTTOM
                    case direction.BOTTOM:
                        {
                            switch (item)
                            {
                                case 'L':
                                    {
                                        dct = direction.RIGHT;

                                        drawCurve(direction.BOTTOM, 'L', x, y);

                                        x += m_SelectedBitMask.Offset;
                                        y += m_SelectedBitMask.Offset - 1;

                                        break;
                                    }
                                case 'R':
                                    {
                                        dct = direction.LEFT;

                                        drawCurve(direction.BOTTOM, 'R', x, y);

                                        x -= m_SelectedBitMask.Offset;
                                        y += m_SelectedBitMask.Offset - 1;

                                        break;
                                    }
                                case 'I':
                                    {
                                        dct = direction.BOTTOM;

                                        drawLine(direction.BOTTOM, x, y);

                                        y += m_SelectedBitMask.Size;

                                        break;
                                    }
                            }
                            break;
                        }
                    #endregion
                }
            } 
            #endregion

            #region cut image
            
            #region get startX
            for (int i = 0; i < m_MaxWidth; i++)
            {
                for (int j = 0; j < m_MaxHeight; j++)
                {
                    if (m_Img.GetPixel(i, j).ToArgb() == m_Col.ToArgb())
                    {
                        startX = i;
                        break;
                    }
                }
                if (startX != 0)
                    break;
            } 
            #endregion

            #region get endX
            for (int i = m_MaxWidth - 1; i >= 0; i--)
            {
                for (int j = 0; j < m_MaxHeight; j++)
                {
                    if (m_Img.GetPixel(i, j).ToArgb() == m_Col.ToArgb())
                    {
                        endX = i;
                        break;
                    }
                }

                if (endX != 0)
                    break;
            } 
            #endregion

            #region get startY
            for (int j = 0; j < m_MaxHeight; j++)
            {
                for (int i = 0; i < m_MaxWidth; i++)
                {
                    if (m_Img.GetPixel(i, j).ToArgb() == m_Col.ToArgb())
                    {
                        startY = j;
                        break;
                    }
                }
                if (startY != 0)
                    break;
            } 
            #endregion

            #region get endY
            for (int j = m_MaxHeight - 1; j >= 0; j--)
            {
                for (int i = 0; i < m_MaxWidth; i++)
                {
                    if (m_Img.GetPixel(i, j).ToArgb() == m_Col.ToArgb())
                    {
                        endY = j;
                        break;
                    }
                }

                if (endY != 0)
                    break;
            } 
            #endregion

            int newWidth = m_MaxWidth - (startX - m_SelectedBitMask.Offset-1) - (m_MaxWidth - endX + 2 - m_SelectedBitMask.Offset);
            int newHeight = m_MaxHeight - (startY - m_SelectedBitMask.Offset-1) - (m_MaxHeight - endY + 2 - m_SelectedBitMask.Offset);

            m_Result = new Bitmap(newWidth, newHeight);

            #region draw new cut image
            for (int i = 0; i < newWidth - 1; i++)
            {
                for (int j = 0; j < newHeight - 1; j++)
                {
                    m_Result.SetPixel(i, j, m_Img.GetPixel(i + startX - (m_SelectedBitMask.Offset - 1), j + startY - (m_SelectedBitMask.Offset - 1)));
                }
            }  
            #endregion

            m_ImgRuli.Source = toWPFBitmap(m_Result);
            
            #endregion
        }

        private BitmapSource toWPFBitmap(Bitmap _bitmap)
        {
            using (MemoryStream stream = new MemoryStream())
            {
                _bitmap.Save(stream, ImageFormat.Bmp);

                stream.Position = 0;
                BitmapImage result = new BitmapImage();
                result.BeginInit();

                result.CacheOption = BitmapCacheOption.OnLoad;
                result.StreamSource = stream;
                result.EndInit();
                result.Freeze();
                return result;
            }
        }

        private void btSave_Click(object sender, RoutedEventArgs e)
        {
            SaveFileDialog save = new SaveFileDialog();
            save.Filter = "Windows Bitmap (*.bmp) |*.bmp";
            save.InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.MyPictures);
            save.RestoreDirectory = false;

            save.ShowDialog();

            if (!string.IsNullOrEmpty(save.SafeFileName))
                m_Result.Save(save.FileName, ImageFormat.Bmp);
        }

        private void cbBitMask_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            m_SelectedBitMask = m_ListBitMasks.Where(p => p.Size == ((BitMask)cbBitMask.SelectedItem).Size).FirstOrDefault();
        }
    }

    public class BitMask
    {
        public int Size { get; set; }
        public int Offset { get; set; }
        public string Title { get; set; }
    }

    enum Curve
    {
        RB,
        RT,
        LB,
        LT
    }

    enum direction
    {
        LEFT,
        TOP,
        RIGHT,
        BOTTOM
    }
}
